"""
This will eventually replace all the `to_dict` methods of the different model
classes we have. This will ensure cleaner code and better
separation of concerns.
"""
from funcy import project

from flask_login import current_user

from redash import models
from redash.permissions import has_access, view_only
from redash.utils import json_loads
from redash.models.parameterized_query import ParameterizedQuery

from .query_result import serialize_query_result, serialize_query_result_to_csv, serialize_query_result_to_xlsx


def public_widget(widget):
    res = {
        'id': widget.id,
        'width': widget.width,
        'options': json_loads(widget.options),
        'text': widget.text,
        'updated_at': widget.updated_at,
        'created_at': widget.created_at
    }

    v = widget.visualization
    if v and v.id:
        res['visualization'] = {
            'type': v.type,
            'name': v.name,
            'description': v.description,
            'options': json_loads(v.options),
            'updated_at': v.updated_at,
            'created_at': v.created_at,
            'query': {
                'id': v.query_rel.id,
                'name': v.query_rel.name,
                'description': v.query_rel.description,
                'options': v.query_rel.options
            }
        }

    return res


def public_dashboard(dashboard):
    dashboard_dict = project(serialize_dashboard(dashboard, with_favorite_state=False), (
        'name', 'layout', 'dashboard_filters_enabled', 'updated_at',
        'created_at'
    ))

    widget_list = (models.Widget.query
                   .filter(models.Widget.dashboard_id == dashboard.id)
                   .outerjoin(models.Visualization)
                   .outerjoin(models.Query))

    dashboard_dict['widgets'] = [public_widget(w) for w in widget_list]
    return dashboard_dict


class Serializer(object):
    pass


class QuerySerializer(Serializer):
    def __init__(self, object_or_list, **kwargs):
        self.object_or_list = object_or_list
        self.options = kwargs

    def serialize(self):
        if isinstance(self.object_or_list, models.Query):
            result = serialize_query(self.object_or_list, **self.options)
            if self.options.get('with_favorite_state', True) and not current_user.is_api_user():
                result['is_favorite'] = models.Favorite.is_favorite(current_user.id, self.object_or_list)
        else:
            result = [serialize_query(query, **self.options) for query in self.object_or_list]
            if self.options.get('with_favorite_state', True):
                favorite_ids = models.Favorite.are_favorites(current_user.id, self.object_or_list)
                for query in result:
                    query['is_favorite'] = query['id'] in favorite_ids

        return result


def serialize_query(query, with_stats=False, with_visualizations=False, with_user=True, with_last_modified_by=True):
    d = {
        'id': query.id,
        'latest_query_data_id': query.latest_query_data_id,
        'name': query.name,
        'description': query.description,
        'query': query.query_text,
        'query_hash': query.query_hash,
        'schedule': query.schedule,
        'api_key': query.api_key,
        'is_archived': query.is_archived,
        'is_draft': query.is_draft,
        'updated_at': query.updated_at,
        'created_at': query.created_at,
        'data_source_id': query.data_source_id,
        'options': query.options,
        'version': query.version,
        'tags': query.tags or [],
        'is_safe': query.parameterized.is_safe,
    }

    if with_user:
        d['user'] = query.user.to_dict()
    else:
        d['user_id'] = query.user_id

    if with_last_modified_by:
        d['last_modified_by'] = query.last_modified_by.to_dict() if query.last_modified_by is not None else None
    else:
        d['last_modified_by_id'] = query.last_modified_by_id

    if with_stats:
        if query.latest_query_data is not None:
            d['retrieved_at'] = query.retrieved_at
            d['runtime'] = query.runtime
        else:
            d['retrieved_at'] = None
            d['runtime'] = None

    if with_visualizations:
        d['visualizations'] = [serialize_visualization(vis, with_query=False)
                               for vis in query.visualizations]

    return d


def serialize_visualization(object, with_query=True):
    d = {
        'id': object.id,
        'type': object.type,
        'name': object.name,
        'description': object.description,
        'options': json_loads(object.options),
        'updated_at': object.updated_at,
        'created_at': object.created_at
    }

    if with_query:
        d['query'] = serialize_query(object.query_rel)

    return d


def serialize_widget(object):
    d = {
        'id': object.id,
        'width': object.width,
        'options': json_loads(object.options),
        'dashboard_id': object.dashboard_id,
        'text': object.text,
        'updated_at': object.updated_at,
        'created_at': object.created_at
    }

    if object.visualization and object.visualization.id:
        d['visualization'] = serialize_visualization(object.visualization)

    return d


def serialize_alert(alert, full=True):
    d = {
        'id': alert.id,
        'name': alert.name,
        'options': alert.options,
        'state': alert.state,
        'last_triggered_at': alert.last_triggered_at,
        'updated_at': alert.updated_at,
        'created_at': alert.created_at,
        'rearm': alert.rearm
    }

    if full:
        d['query'] = serialize_query(alert.query_rel)
        d['user'] = alert.user.to_dict()
    else:
        d['query_id'] = alert.query_id
        d['user_id'] = alert.user_id

    return d


def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state=True):
    layout = json_loads(obj.layout)

    widgets = []

    if with_widgets:
        for w in obj.widgets:
            if w.visualization_id is None:
                widgets.append(serialize_widget(w))
            elif user and has_access(w.visualization.query_rel, user, view_only):
                widgets.append(serialize_widget(w))
            else:
                widget = project(serialize_widget(w),
                                 ('id', 'width', 'dashboard_id', 'options', 'created_at', 'updated_at'))
                widget['restricted'] = True
                widgets.append(widget)
    else:
        widgets = None

    d = {
        'id': obj.id,
        'slug': obj.slug,
        'name': obj.name,
        'user_id': obj.user_id,
        # TODO: we should properly load the users
        'user': obj.user.to_dict(),
        'layout': layout,
        'dashboard_filters_enabled': obj.dashboard_filters_enabled,
        'widgets': widgets,
        'is_archived': obj.is_archived,
        'is_draft': obj.is_draft,
        'tags': obj.tags or [],
        # TODO: bulk load favorites
        'updated_at': obj.updated_at,
        'created_at': obj.created_at,
        'version': obj.version
    }

    if with_favorite_state:
        d['is_favorite'] = models.Favorite.is_favorite(current_user.id, obj)

    return d
